#ifndef NET_SOCKET_ADDRESS_H_
#define NET_SOCKET_ADDRESS_H_

#include <netinet/in.h>
#include <sys/socket.h>

#include <cstdint>
#include <cstring>
#include <iosfwd>
#include <string>
#include <vector>
#include <cstdlib>

namespace fermat {
namespace net {
class SocketAddress {

public:
    /*!
     * Create empty invalid address object by clearing all bytes.
     */
    SocketAddress();
    /*!
     * set object by given inet address.
     * @author lothar<liyinbin> 2017-04-01
     * @param  sa    address.
     * @param  salen address memory len.
     * @return       address valid or not.
     */
    bool set(struct sockaddr* sa, socklen_t salen);

    /*!
     * set  socket address object and resolve the given host:port
     * @author lothar<liyinbin> 2017-04-01
     * @param  hostport host port string, eg."129.168.2.12:80"
     * @return          address valid or not.
     */
    bool set(const std::string& hostport);

    /*!
     * a socket address object and resolve the given
     * @author lothar<liyinbin> 2017-04-01
     * @param  hostname    host name for address, eg. "www.example.com".
     * @param  servicename port for address, eg."90090".
     * @return             address valid or not.
     */
    bool set(const char* hostname, const char* servicename);

    /*!
     * pointer to enclosed address as a generic sockattr struct.
     * @author lothar<liyinbin> 2017-04-01
     * @return the address pointer.
     */
    const struct sockaddr* sockaddr() const;

    /*!
     * pointer to enclosed address as a generic sockattr struct.
     * @author lothar<liyinbin> 2017-04-01
     * @return the address pointer.
     */
    struct sockaddr* sockaddr();

    /*!
     * total length of enclosed sockaddr structure.
     * @author lothar<liyinbin> 2017-04-01
     * @return the length.
     */
    socklen_t socklen() const;
   
    /*!
     * the enclosed socket address is a valid IPv4 or IPv6
     * @author lothar<liyinbin> 2017-04-01
     * @return true if valid.
     */
    bool isValid() const;

    /*!
    * return true if the enclosed socket address is a IPv4 address.
    */
    
    /*!
     * test if enclosed socket address is a IPv4 address.
     * @author lothar<liyinbin> 2017-04-01
     * @return true if it is ipv4.
     */
    bool isIPv4() const;

    /*!
     * test if enclosed socket address is a IPv6 address.
     * @author lothar<liyinbin> 2017-04-01
     * @return true if it is ipv6.
     */
    bool isIPv6() const;

    /*!
     * Cast the enclosed sockaddr into the sockaddr_in IPv4 structure.
     * @author lothar<liyinbin> 2017-04-01
     * @return [the pointer
     */
    const struct sockaddr_in * sockaddr_in() const;

    /*!Cast the enclosed sockaddr into the sockaddr_in6 IPv6 structure.
     * @author lothar<liyinbin> 2017-04-01
     * @return the pointer
     */
    const struct sockaddr_in6* sockaddr_in6() const;

    /*!
     * the enclosed socket address as a string without the port number.
     * @author lothar<liyinbin> 2017-04-01
     * @return host
     */
    std::string toStringHost() const;


    /*!
     * the enclosed socket address as a string with the port number.
     * @author lothar<liyinbin> 2017-04-01
     * @return host and port
     */
    std::string toStringHostPort() const;

    /*!
     * Make the socket address ostream-able: outputs address:port
     */
    friend std::ostream& operator << (std::ostream& os,
                                      const SocketAddress& sa);
    /*!
     * the currently set port address in host byte-order.
     * @author lothar<liyinbin> 2017-04-01
     * @return port
     */
    uint16_t getPort() const;

    /*!
     * change the current port address
     * @author lothar<liyinbin> 2017-04-01
     * @param  port et 80
     */
    void setPort(uint16_t port);

    /*!
     * Resolve the given host name
     * @author lothar<liyinbin> 2017-04-01
     * @param  hostname    the host name
     * @param  servicename port str if exists
     * @return             true if sucess
     */
    bool resolve(const char* hostname,
                 const char* servicename = NULL);
    
    /*!
     * Resolve the given host name
     * @author lothar<liyinbin> 2017-04-01
     * @param  hostname    the host name
     * @param  servicename port str if exists
     * @return             true if sucess
     */
    static SocketAddress resolveOne(const char* hostname,
                                    const char* servicename = NULL);

    /*!
     * Resolve the given host name
     * @author lothar<liyinbin> 2017-04-01
     * @param  hostname    the host name
     * @param  servicename port str if exists
     * @return             true if sucess
     */
    static SocketAddress resolveWithPort(const char* hostname,
                                         const char* defaultservice);

    /*!
     * Resolve the given host name
     * @author lothar<liyinbin> 2017-04-01
     * @param  hostname    the host name
     * @param  servicename port str if exists
     * @return             true if sucess
     */
    static std::vector<SocketAddress>
    resolveAll(const char* hostname, const char* servicename = NULL);
    
    /*!
     * textual message of the last error occurring in the resolve
     * method. 
     * @author lothar<liyinbin> 2017-04-01
     * @return pointer
     */
    const char * getResolveError() const;

    /*!
     * Cast the enclosed sockaddr into the sockaddr_in IPv4 structure.
     * @author lothar<liyinbin> 2017-04-01
     * @return [the pointer
     */
    struct sockaddr_in * sockaddr_in();

    /*!Cast the enclosed sockaddr into the sockaddr_in6 IPv6 structure.
     * @author lothar<liyinbin> 2017-04-01
     * @return the pointer
     */
    struct sockaddr_in6* sockaddr_in6();
protected:
    /*!
     * Enclosed IPv4, IPv6 or other socket address structure. Used as a struct
     * sockaddr_in or a struct sockaddr_in6. sizeof(sockaddr_in6) is
     * (currently) 28.
     */
    union SockAddrUnion {
        struct sockaddr     generic;
        struct sockaddr_in  in;
        struct sockaddr_in6 in6;
    } _sockaddr;
private:
    int _resolve_error;
};


inline const struct sockaddr* SocketAddress::sockaddr() const 
{
    return &_sockaddr.generic;
}

inline struct sockaddr* SocketAddress::sockaddr()
{
     return &_sockaddr.generic;
}

inline socklen_t SocketAddress::socklen() const 
{
    return socklen_t(
        sockaddr()->sa_family == AF_INET ? sizeof(_sockaddr.in) :
        sockaddr()->sa_family == AF_INET6 ? sizeof(_sockaddr.in6) :
        0);
}


inline  bool SocketAddress::isValid() const 
{
    return (sockaddr()->sa_family == AF_INET) ||
               (sockaddr()->sa_family == AF_INET6);
}

inline bool SocketAddress::isIPv4() const 
{
    return (sockaddr()->sa_family == AF_INET);
}

inline bool SocketAddress::isIPv6() const 
{
    return (sockaddr()->sa_family == AF_INET6);
}

inline const struct sockaddr_in * SocketAddress::sockaddr_in() const 
{
    return &_sockaddr.in;
}

inline struct sockaddr_in6* SocketAddress::sockaddr_in6() 
{
    return &_sockaddr.in6;
}

inline struct sockaddr_in * SocketAddress::sockaddr_in() 
{
    return &_sockaddr.in;
}

inline const struct sockaddr_in6* SocketAddress::sockaddr_in6() const 
{
    return &_sockaddr.in6;
}

inline uint16_t SocketAddress::getPort() const 
{
    if (sockaddr()->sa_family == AF_INET) {
        return ntohs(sockaddr_in()->sin_port);
    } else if (sockaddr()->sa_family == AF_INET6) {
        return ntohs(sockaddr_in6()->sin6_port);
    } else {
        return 0;
    }
}

inline void SocketAddress::setPort(uint16_t port) 
{
    if (sockaddr()->sa_family == AF_INET) {
        sockaddr_in()->sin_port = htons(port);
    } else if (sockaddr()->sa_family == AF_INET6) {
        sockaddr_in6()->sin6_port = htons(port);
    }

}

} //namespace net
} //namespace fermat
#endif 
